#pragma once
#include "index.hpp"
#include "util.hpp"
#include <algorithm>
#include <jsoncpp/json/json.h>



namespace ns_searcher
{

	struct InvertedElemPrint
	{
		//文档去重结构体
		uint16_t doc_id;
		int weight;
		std::vector<std::string> words;
		InvertedElemPrint()
		:doc_id(0)
		,weight(0)
		{}
	};
	class Searcher
	{
	public:
		Searcher() {}
		~Searcher() {}
		
		void InitSearcher(const std::string& input)
		{
			//1.获取index单例
			index = ns_index::Index::GetInstance();
			//std::cout << "获取单例成功..." << std::endl;
			LOG(NORMAL,"获取index单例成功...");
			//2.根据index对象建立索引
			index->BuildIndex(input);
			//std::cout<< "建立正排倒排索引成功" << std::endl;
			LOG(NORMAL,"建立正排倒排索引成功...");
		}

		//query:搜索的关键字
		//json string: 返回给用户浏览器的搜索结果
		void Search(const std::string& query,std::string* json_string)
		{
			//1.分词：对query按照searcher的要求进行分词
			std::vector<std::string> words;
			ns_util::JiebaUtil::CutString(query,&words);

			//2.触发：根据分词后的各个词，进行index查找 注：索引忽略大小写，搜索时关键字也不应区分大小写！
			//ns_index::InvertedList inverted_list_all; 内部元素为ns_index::InvertedElem,在这里表示所有InvertedElem的集合
			std::vector<InvertedElemPrint> inverted_list_all;//用于保存不重复倒排拉链节点的vector
			std::unordered_map<uint64_t,InvertedElemPrint> tokens_map;
			
			for(std::string word : words)
			{
				//用户输入的信息也要全小写处理，因为在之前建立倒排索引时，InvertedElem内的word就是小写的，方便对应
				boost::to_lower(word);
				//根据关键词分词内容依次获取其对应的倒排索引
				ns_index::InvertedList* inverted_list = index -> GetInvertedList(word);
				if(nullptr == inverted_list)
				{
					//若该关键字没有倒排索引，继续向下寻找下一个单词
					continue;
				}
				//inverted_list_all.insert(inverted_list_all.end(),inverted_list->begin(),inverted_list->end());这里要注意一个问题：当用户输入的搜索内容被分为多个单词，而这多个单词指向同一个文档时，搜索结果就会出现重复，即多个搜索结果对应同一个文本。为了避免这种情况，不能简单的将所有内容添加到inverted_list_all中。
				for(const auto& elem: *inverted_list)
				{
					auto& item = tokens_map[elem.doc_id];//存在则获取，不存在则创建
					//此时item一定是doc_id相同的print节点！！
					item.doc_id = elem.doc_id;//考虑新建的情况
					item.weight += elem.weight;
					item.words.push_back(elem.word);
				}
			}

			for(const auto& item: tokens_map)
			{
				inverted_list_all.push_back(std::move(item.second));
			}

			//3.合并排序：汇总查找结果，按照相关性（weight）降序排序
			/*std::sort(inverted_list_all.begin(),inverted_list_all.end(),
			[](const ns_index::InvertedElemPrint e1,const ns_index::InvertedElem e2){
				return e1.weight > e2.weight;
			});*/
			std::sort(inverted_list_all.begin(),inverted_list_all.end(),[](const InvertedElemPrint& e1,const InvertedElemPrint& e2){
				return e1.weight > e2.weight;
			});
			//4.构建：根据查找出的结果构建json串（第三方json库jsoncpp——通过jsoncpp完成序列化与反序列化）
			Json::Value root;//Json序列化的中间变量，搜索结果的总集合
			for(auto& item : inverted_list_all)
			{
				//根据倒排索引得到的文档id寻找正排索引，找到其文档内容
				ns_index::DocInfo* doc = index->GetForwardindex(item.doc_id);
				if(nullptr == doc)
				{
					continue;
				}
				Json::Value elem;//单次的一个搜索结果
				elem["title"] = doc->title;
				elem["desc"] = GetDesc(doc->content,item.words[0]);//这里显示的是正文的一部分(摘要),这个摘要最好要凸显关键字。以单词数组中的0号元素构建索引。
				elem["url"] = doc->url;
				root.append(elem);//将一个搜索结果加入到所有搜索结果的集合中
			}
			//这里用writer写入搜索结果
			Json::FastWriter writer;
			*json_string = writer.write(root);
		}
		
		std::string GetDesc(const std::string& html_content,const std::string& word)
		{
			//逻辑：找到word在html_content中首次出现的位置，然后向前寻找50个字节、向后寻找100字节，并将这部分内容截取返回。若向前不足，就从begin开始；若向后不足，就直到end。
			const std::size_t prev_step = 50;
			const std::size_t next_step = 100;
			//1.找到首次出现的位置
			//std::size_t pos = html_content.find(word);	这里要注意一个坑：html_content是从正排索引获得的html文档正文，是源文档未加任何改动的原本内容；而word是从倒排索引InvertedElem中获取的word，但是！！这个word为了匹配用户不区分大小写的输入，已经做了全小写处理！！而string的find函数并不自动屏蔽大小写，因此可能出现源文档中出现Split、倒排索引中有split，但是find函数并未找到任何内容的情况！！所以这里采用std::search接口。
			auto iter = std::search(html_content.begin(),html_content.end(),word.begin(),word.end(),
			[](int x,int y){ return (std::tolower(x) == std::tolower(y));});
			//在前两个迭代器区间寻找后两个迭代器区间的元素的第一个出现位置，然后自己定义一个忽略大小的比较函数
			if(iter == html_content.end())
			{
				//这种情况是不可能发生的，不过仍然处理一下
				return "None";
			}
			std::size_t pos = std::distance(html_content.begin(),iter);
			//2.获取start、end
			//默认begin和end为正文起始和结尾
			std::size_t start = 0;
			std::size_t end = html_content.size() - 1;
			//如果之前有50+字符，就更新起始位置;如果之后有100+字符，就更新结束位置
			//if(pos - prev_step > start) 注意：这里是个坑，因为size_t是无符号整形，如果如此书写，即使前面的字符不足(pos-prev< 0)，负数也是远大于0的。因此要用下面这种方式。
			if(pos > start + prev_step)
				start = pos - prev_step;
			if(pos + next_step < end)//这里也要注意上一个if的问题
				end = pos + next_step;
			//3.截取子串返回
			if(start >=  end)
			{
				//也几乎是不可能的，做一下处理
				return "None";
			}
			std::string desc =  html_content.substr(start,end-start);
			desc += "...";
			return desc;
		}

	private:
		ns_index::Index *index;  //供系统进行查找的索引
	};
}














